﻿using System;
using System.Collections.Generic;

using System.Reflection;
using MBT.Modeling;
using CoreDES.Utils;
using SMLib.Base;
using CoreDES.Execution;
using CoreDES.Modeling;


namespace MBT.Modeling
{
    /// <summary>
    /// This class gets an array of models (object with some state variables and actions) and
    /// makes the required structures and objects and also create the state object (containing
    /// all state variables and required code for "Equals", "Clone", etc.)
    /// </summary>
    public class ModelCreator
    {

        TestModel mo;
        static Dictionary<object, bool> objIsRegistered = new Dictionary<object, bool>();
        //static Dictionary<Type, int> classlevel = new Dictionary<Type, int>();
        AtomicTestModel[] mysdes;
        List<ExecutableMethod> functions = new List<ExecutableMethod>();
        //Type currentType;
        List<Type> submodels = new List<Type>();


        /// <summary>
        /// The method to be called to create a Test model giving an array of model objects
        /// </summary>
        /// <param name="o">The array of model objects</param>
        /// <returns>the created test model</returns>
        public TestModel CreateComposed(object[] o)
        {

            List<AtomicTestModel> l = new List<AtomicTestModel>();

            for (int i = 0; i < o.Length; i++)
            {
                bool isReg = false;
                if (objIsRegistered.TryGetValue(o[i], out isReg))
                {
                    throw new Exception("Double Registration of same object!");
                }
                else
                    objIsRegistered.Add(o[i], true);

                AtomicTestModel at = CreateAtomic(o[i]);
                at.ExecutableObject = o[i];
                l.Add(at);
            }
            mo = new TestModel(l.ToArray());
            mysdes = l.ToArray();
            GenerateCode4StateClass(mo);
            return mo;
        }

        /// <summary>
        /// Create atomic test model from one object (reading state variables and methods from object as a library)
        /// </summary>
        /// <param name="o">object model</param>
        /// <returns>the generated atomic test model</returns>
        AtomicTestModel CreateAtomic(object o)
        {
            AtomicTestModel atom1 = new AtomicTestModel();
            atom1.ExecutableObject = o;

            //currentType = o.GetType();

            ExtractMethods(o.GetType(), atom1);
            ExtractVariables(o.GetType(), atom1);
            ExtractSubModels(o.GetType(), atom1);
            ExtractRefModels(o.GetType(), atom1);

            return atom1;
        }


        /// <summary>
        /// Check whether a fieldinfo has an specific attribute or not
        /// </summary>
        /// <param name="info">the fieldinfo</param>
        /// <param name="t">the type of attribute we are investigating</param>
        /// <returns>true if it has, false in other case</returns>
        bool HasAttribute(FieldInfo info, Type t)
        {
            return (System.Attribute.GetCustomAttributes(info, t) == null);
        }

        /// <summary>
        /// Check whether a methodinfo has an specific attribute or not
        /// </summary>
        /// <param name="info">the MethodInfo</param>
        /// <param name="t">the type of attribute we are investigating</param>
        /// <returns>true if it has, false in other case</returns>
        bool HasAttribute(MethodInfo info, Type t)
        {
            return (System.Attribute.GetCustomAttributes(info, t) == null);
        }


        bool HasAttribute(System.Attribute[] attrs, Attribute type)
        {
            foreach (System.Attribute a in attrs)
            {
                if (a == type)
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Generate code for a test model
        /// </summary>
        /// <param name="model">the test model</param>
        /// <returns>generated code</returns>
        string GenerateCode4StateClass(TestModel model)
        {
			if(model==null)
			{
				throw new Exception("The model is null!");				
			}
			
            string code = "using System;\nusing SMLib.Base;\n";

            code += "namespace MyNamespace{\n\tpublic class state\n{\n\t\tint hashCode;";
            int i = -1;
            foreach (object o in objIsRegistered.Keys)
            {
                i++;
                code += "\n\t\t" + o.GetType().ToString() + " model" + i.ToString() + ";";
            }

            string eqCode = "\n\t\tpublic override bool Equals(object obj){" +
                "\n\t\t\tif(!(obj is state)) return false;" +
                "\n\t\t\tstate tempState=(state)obj;";


            string cloneCode = "\n\t\tpublic object Clone(){\n\t\t\tstate st=new state();";


            string cloneToCode = "\n\t\tpublic void CloneTo(";// +currentType.FullName + " model){";

            string constState = "\n\t\tpublic state(){";
            i = -1;
            foreach (object o in objIsRegistered.Keys)
            {
                i++;
                constState += "\n\t\t\t" +/*currentType.FullName o.GetType().ToString() + */"this.model" + i.ToString() + "=new " +
                    /*currentType.FullName*/ o.GetType().ToString() + "();";
            }

            string constState2 = "\n\t\tpublic state(";
            string constState2Body = "";
            i = -1;
            foreach (object o in objIsRegistered.Keys)
            {
                i++;
                if (i == 0)
                {
                    constState2 += /*currentType.FullName*/ o.GetType().ToString() + " model" + i.ToString();
                    cloneToCode += /*currentType.FullName*/ o.GetType().ToString() + " model" + i.ToString();
                    //Add State Variables
                    //code += o.GetType().ToString();
                }
                else
                {
                    constState2 += "," +/*currentType.FullName*/ o.GetType().ToString() + " model" + i.ToString();
                    cloneToCode += "," +/*currentType.FullName*/ o.GetType().ToString() + " model" + i.ToString();
                }

                constState2Body += "\n\t\t\tthis.model" + i.ToString() + " = new " + /*currentType.FullName*/ o.GetType().ToString() + "();";
            }
            constState2 += "){" + constState2Body;
            cloneToCode += "){";

            string fieldsCode = "\n\t\tpublic object[] Fields{ \n\t\t\tget { return new object[] {";

            string nameCode = "\n\t\tpublic string ModelName{ get{ return \"Model1\"; }}";

            string GetLocNameCode = "\n\t\tpublic string GetLocationName(int locid){";
            string GetLocValueCode = "\n\t\tpublic int LocationValuesCount { get{ return " + mysdes[0].Variables.Variables.Count + "; } }";
            string GetLocCode = "\n\t\tpublic object GetLocation(int locid){";

            string compaerCode = "\n\t\tpublic int CompareTo(object obj){if (Equals(obj))return 0; else return -1;}";

            int mNumber = -1;
            foreach (AtomicTestModel m in mysdes)
            {
                mNumber++;
                i = 0;
                if (mNumber > 0)
                    fieldsCode += ", ";
                foreach (Variable v in m.Variables.Variables)
                {
                    if (v.Info.FieldType.IsPrimitive || v.Info.FieldType.IsEnum || v.Info.FieldType == typeof(string))
                    {
                        eqCode += "\n\t\t\tif(!(tempState.model" + mNumber.ToString() + "." + v.ID + ".Equals(model" + mNumber.ToString()
                            + "." + v.ID + ")))return false;";
                        cloneCode += "\n\t\t\tst.model" + mNumber.ToString() + "." +
                            v.ID + " = " + " model" + mNumber.ToString() + "." + v.ID + ";";
                        cloneToCode += "\n\t\t\tmodel" + mNumber.ToString() + "." +
                            v.ID + " = " + " this.model" + mNumber.ToString() + "." + v.ID + ";";
                    }
                    else
                    {
                        string gtype = "";
                        if (v.Info.FieldType.IsGenericType)
                        {
                            //"`x[" is for generic typesOfEnums and "+" is for enum type
                            gtype = v.Info.FieldType.ToString().
                                Replace("`1[", "<").Replace("`2[", "<").Replace("`3[", "<").Replace(']', '>').Replace('+', '.');
                        }
                        else
                            gtype = v.Info.FieldType.ToString();

                        cloneCode += "\n\t\t\tst.model" + mNumber.ToString() + "." + v.ID + " = (" + gtype + ") model" +
                            mNumber.ToString() + "." + v.ID + ".Clone();";
                        cloneToCode += "\n\t\t\tmodel" + mNumber.ToString() + "." + v.ID + " = (" + gtype + ")" + " this.model" +
                            mNumber.ToString() + "." + v.ID + ".Clone();";

                        eqCode += "\n\t\t\tif((tempState.model" + mNumber.ToString() + "." + v.ID + "== null && model" +
                            mNumber.ToString() + "." + v.ID +
                            " != null)||(model" + mNumber.ToString() + "." + v.ID + "==null && tempState.model" + mNumber.ToString() + "." +
                            v.ID + "!=null))return false;";

                        eqCode += "\n\t\t\tif((tempState.model" + mNumber.ToString() + "." + v.ID + "!=null && model" +
                            mNumber.ToString() + "." + v.ID +
                         "!=null)&& !(tempState.model" + mNumber.ToString() + "." + v.ID + ".Equals(model" +
                         mNumber.ToString() + "." + v.ID + ")))return false;";
                    }

                    //cloneToCode += "\n\t\t\tmodel." + v.ID + " = " + " this.model." + v.ID + ";";
                    constState2 += "\n\t\t\tthis.model" + mNumber.ToString() + "." + v.ID + " = " + " model" +
                        mNumber.ToString() + "." + v.ID + ";";

                    GetLocNameCode += "\n\t\t\tif(locid == " + i.ToString() + ") return \"" + v.ID + "\";";
                    GetLocCode += "\n\t\t\tif(locid == " + i.ToString() + ") return model" + mNumber.ToString() + "." + v.ID + ";";

                    i++;
                    if (i > 1)
                        fieldsCode += ", model" + mNumber.ToString() + "." + v.ID;
                    else
                        fieldsCode += "model" + mNumber.ToString() + "." + v.ID;

                    //if (i == m.Variables.Variables.Count)
                    //    fieldsCode += "}";
                }
            }
            constState += "\n\t\t}";
            constState2 += "\n\t\t}";
            fieldsCode += "};}\n\t\t}";
            eqCode += "\n\t\t\treturn true;\n\t\t}";
            cloneCode += "\n\t\t\treturn st;\n\t\t}";
            cloneToCode += "\n\t\t}";
            GetLocNameCode += "\n\t\treturn null;\n\t\t}";
            GetLocCode += "\n\t\treturn null;\n\t\t}";

            //string Cont = "public object Content{ get{return this;}}";
            code += constState + constState2 + fieldsCode + eqCode + cloneCode + cloneToCode +
                GetLocNameCode + GetLocValueCode + GetLocCode + nameCode + compaerCode
                + "\n\t}\n}";

            string tempDir = System.IO.Path.GetTempPath();
			
            System.IO.Directory.CreateDirectory(tempDir + "/CoreDES/");
            //tempDir = System.IO.Directory.GetCurrentDirectory() + "\\TEMP\\";
			tempDir +="CoreDES/";

            System.IO.File.WriteAllText(tempDir + "tempCode.cs", code);

            CompilerHelper cmp = new CompilerHelper();
			foreach(string st in libs)
				cmp.AddLibrary(st);
			
            //cmp.AddLibrary(System.IO.Directory.GetCurrentDirectory() + "\\MBT.dll");
			string appPath = 
				System.IO.Path.GetDirectoryName (System.Reflection.Assembly.GetExecutingAssembly ().Location);
				//System.IO.Path.GetDirectoryName(Assembly.GetAssembly(typeof(ModelCreator)).CodeBase);
			cmp.AddLibrary(appPath + "/SMLib.dll");
			cmp.AddLibrary(appPath + "/CoreDES.dll");

            cmp.AddSource(tempDir + "tempCode.cs");
            cmp.Compile();
            model.SetStateType((cmp.GenerateExecutable("MyNamespace.state")).GetType());

            return code;
        }
		
		List<string> libs=new List<string>();
		//List<string> sources;
			
			
		public void AddLibrary(string lib)
		{
			libs.Add(lib);
		}
		
        enum functionType { precondition, execution };

        /// <summary>
        /// create set of evets from the (obtained) list of functions and store it in "events"
        /// </summary>
        void ConcludeEvents(AtomicTestModel atMod)
        {
            while (functions.Count > 0)
            {
                string evID;
                functionType funcType = functionType.execution;
                int j = 0;
                int numberOfErrors = 0;
                bool isControllable = true;
                evID = functions[0].ID;
                System.Attribute[] attrs = System.Attribute.GetCustomAttributes(functions[0].info);

                foreach (System.Attribute at in attrs)
                {
                    if (at is MBT.Modeling.ActionAttrib.Exec)
                    {
                        funcType = functionType.execution;
                        evID = (at as MBT.Modeling.ActionAttrib.Exec).actionID;
                        isControllable = (at as MBT.Modeling.ActionAttrib.Exec).isControllable;
                        break;
                    }
                    else if (at is MBT.Modeling.ActionAttrib.Pre)
                    {
                        funcType = functionType.precondition;
                        evID = (at as MBT.Modeling.ActionAttrib.Pre).actionID;
                        break;
                    }
                }

                if (funcType == functionType.execution)
                {
                    int numberOfmethods = 0;
                    for (int i = 1; i < functions.Count; i++)
                    {
                        attrs = System.Attribute.GetCustomAttributes(functions[i].info);
                        foreach (System.Attribute at in attrs)
                        {
                            if (at is MBT.Modeling.ActionAttrib.Exec)
                            {
                                if ((at as MBT.Modeling.ActionAttrib.Exec).actionID == evID)
                                {
                                    //Error!
                                    //we do not expect another Exe method with the same ID
                                    numberOfErrors++;
                                    break;
                                }
                            }
                            else if (at is MBT.Modeling.ActionAttrib.Pre)
                            {
                                if ((at as MBT.Modeling.ActionAttrib.Pre).actionID == evID)
                                {
                                    j = i;
                                    numberOfmethods++;
                                    break;
                                }
                            }
                        }
                    }
                    if (numberOfmethods != 1)
                    {
                        //We cannot find the proper function to make a pair!
                        numberOfErrors++;
                    }
                }
                else if (funcType == functionType.precondition)
                {
                    int numberOfmethods = 0;
                    for (int i = 1; i < functions.Count; i++)
                    {
                        attrs = System.Attribute.GetCustomAttributes(functions[i].info);
                        foreach (System.Attribute at in attrs)
                        {
                            if (at is MBT.Modeling.ActionAttrib.Pre)
                            {
                                if ((at as MBT.Modeling.ActionAttrib.Pre).actionID == evID)
                                {
                                    //Error!
                                    //we do not expect another Pre method with the same ID
                                    numberOfErrors++;
                                    break;
                                }
                            }
                            else if (at is MBT.Modeling.ActionAttrib.Exec)
                            {
                                if ((at as MBT.Modeling.ActionAttrib.Exec).actionID == evID)
                                {

                                    j = i;
                                    numberOfmethods++;
                                    break;
                                }
                            }
                        }
                    }
                    if (numberOfmethods != 1)
                    {
                        //We cannot find the proper function to make a pair!
                        numberOfErrors++;
                    }
                }
                if (numberOfErrors == 0)
                {
                    if (funcType == functionType.execution)
                    {
                        TestEvent ev =
                            new TestEvent(evID, functions[j].info, functions[0].info, atMod.Slicer, isControllable, atMod.ExecutableObject);
                        //ev._isControllable = _isControllable;
                        //ev.slicer = ;
                        //ev.executableObject = ;
                        atMod.Events.Add(ev);
                    }
                    else
                    {
                        TestEvent ev =
                            new TestEvent(evID, functions[0].info, functions[j].info, atMod.Slicer, isControllable, atMod.ExecutableObject);
                        //ev._isControllable = _isControllable;
                        //ev.slicer = atMod.Slicer;
                        //ev.executableObject = atMod.ExecutableObject;
                        atMod.Events.Add(ev);
                    }
                    functions.RemoveAt(j);
                    functions.RemoveAt(0);
                }
                else
                {
                    //Error!
                    break;
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="t"></param>
        /// <param name="atMod"></param>
        void ExtractMethods(Type t, AtomicTestModel atMod)
        {
            //throw new Exception("Correct it!");
            //foreach (System.Reflection.MethodInfo inf in GetMethodsForActions(t))
            foreach (System.Reflection.MethodInfo inf in ReflectionHelper.GetMethods(t) /*GetMethodsForActions(t)!!!*/)
            {
                //Console.WriteLine(inf);
                System.Attribute[] attrs = System.Attribute.GetCustomAttributes(inf);
                int numberOfAtt = 0;
                bool isRegisterd = false;
                foreach (System.Attribute at in attrs)
                {
                    if (at is MBT.Modeling.ActionAttrib.Exec)
                    {
                        numberOfAtt++;
                        //isRegisterd = true;
                    }
                    else if (at is MBT.Modeling.ActionAttrib.Pre)
                    {
                        numberOfAtt++;
                        //isRegisterd = true;
                    }
                    else if (at is EnGineVariables.FinalStateCondition)
                    {
                        //! check that there is not any other formalism related attribute
                        //atMod.Variables.isFinalState = new ExecFunction(inf);
                        numberOfAtt++;
                        isRegisterd = true;
                    }
                    else if (at is EnGineVariables.ValidStateCondition)
                    {
                        //! check that there is not any other formalism related attribute
                        //atMod.Variables.isValidState = new ExecFunction(inf);
                        numberOfAtt++;
                        isRegisterd = true;
                    }
                    else if (at is EnGineVariables.SkipStateCondition)
                    {
                        //! check that there is not any other formalism related attribute
                        //?
                        atMod.Slicer.RegisterSkipState(inf);
                        numberOfAtt++;
                        isRegisterd = true;
                    }
                    else if (at is SkipTransitionCondition)
                    {
                        //! check that there is not any other formalism related attribute
                        //?
                        //We should implement this as the stengen method!
                        numberOfAtt++;
                        isRegisterd = true;
                    }
                    else if (at is DomainDef)
                    {
                        atMod.Slicer.RegisterDomain(inf);
                        numberOfAtt++;
                        isRegisterd = true;
                    }
                }

                if (numberOfAtt > 1)
                {
                    throw new Exception("Error: Multiple duty for a single method (" + inf.Name + ")!");
                }
                else if (numberOfAtt == 1 && !isRegisterd)
                {
                    //the function is not registered before!
                    functions.Add(new ExecutableMethod(inf));
                }
                //else if (numberOfAtt == 0) do nothing!
            }
            ConcludeEvents(atMod);
            functions.Clear();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="t"></param>
        /// <param name="atMod"></param>
        void ExtractSubModels(Type t, AtomicTestModel atMod)
        {
            Console.WriteLine("Extracting submodels (" + t.ToString() + ")");
            //foreach (System.Reflection.FieldInfo inf in ReflectionHelper.GetVariableswithAttr(t, null) /*!!!ReflectionHelper.GetSubModels(t)*/)
            //{
            //    submodels.Add(inf.FieldType);
            //    //Console.WriteLine(inf.ToString());
            //}
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="t"></param>
        /// <param name="atMod"></param>
        void ExtractRefModels(Type t, AtomicTestModel atMod)
        {
            Console.WriteLine("Extracting ref-models (" + t.ToString() + ")");
            //foreach (System.Reflection.FieldInfo inf in ReflectionHelper.GetModelReferences(t))
            //{
            //    //Console.WriteLine(inf.ToString());
            //}
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="t"></param>
        /// <param name="atMod"></param>
        void ExtractVariables(Type t, AtomicTestModel atMod)
        {
            Console.WriteLine("Extracting state variables (" + t.ToString() + ")");

            foreach (System.Reflection.FieldInfo inf in ReflectionHelper.GetVariables(t))
            {
                //[To do]:implement some attributes for variables
                if (inf.IsStatic)
                    continue;

                if (!(inf.FieldType.IsPrimitive || (inf.FieldType == typeof(string)) || (inf.FieldType.IsEnum)
                    || (inf.FieldType.GetInterface(typeof(IStateVariable).FullName) != null)))
                {
                    //Not a state variable, check that if the model is submodel or reference mode
                    if (HasAttribute(inf, typeof(ReferenceModel)))
                    {
                        continue;
                    }
                    else if (HasAttribute(inf, typeof(SubModel)))
                    {
                        //Submodel: We have to extract information!
                        submodels.Add(inf.FieldType);
                        continue;
                    }
                    else
                        throw new Exception("The type of state varible should be builtin, string or IStateWeak!");
                }
                Variable var = new Variable(inf.Name, inf.FieldType.Name, inf);
                atMod.Variables.Variables.Add(var);
            }
        }



    }
}
